import fs from 'fs-extra';
import path from 'path';
import xmljs from 'xml-js';
import { indent, makeComments, readXml } from '../tools/vendor.js';
import { ConvertOption, EntryDef, MacrosDef, StructDef, StructDefSet, StructWrap, ConvertListDoc, ConvertResNode } from '../typings';
import { cname_ClientUsed, isComplexType, isLiteralStringType, NumberLike, StringLike } from './def.js';
import { Alert } from '../tools/alert.js';
import { toolchain } from '../tools/toolchain.js';
import _ from 'lodash';

interface IRelatedEntry {
    struct: string
    entry: EntryDef
}

export interface IConvInfo {
    name: string
    meta: string
    xlsx: string
    xml: string
    sheet: string
    bin: string
    sort: 'Asc' | 'Desc'
}

interface IErrorInfo {
    msg: string
    file?: string
}

export abstract class BaseStructParser {

    protected m_structDefMap: { [structName: string]: StructWrap } = {};
    protected m_cliStructs: string[] = [];

    protected m_structParentMap: { [structName: string]: string[] } = {};

    protected m_macroFileMap: { [fileName: string]: { map: { [macroName: string]: MacrosDef }, list: MacrosDef[] } } = {};

    protected m_macroEntryMap: { [macroName: string]: IRelatedEntry[] } = {};

    protected convMap: { [struct: string]: IConvInfo } = {};

    private blacklist: string[] = [];

    private errors: IErrorInfo[] = [];

    protected get rmFlashTail(): boolean {
        return true;
    }

    protected reset(): void {
        this.m_structDefMap = {};
        this.m_cliStructs.length = 0;
        this.m_structParentMap = {};
        this.m_macroFileMap = {};
        this.m_macroEntryMap = {};
        this.convMap = {};
        this.blacklist.length = 0;
        this.errors.length = 0;
    }

    public async readBlacklist(file: string, filter?: (value: string) => boolean): Promise<void> {
        if (fs.existsSync(file)) {
            const blacklistContent = await fs.readFile(file, 'utf-8');
            this.blacklist = blacklistContent.split(/\r?\n/);
            if (filter != null) this.blacklist = this.blacklist.filter(filter);
        }
    }

    public structIgnored(structName: string): boolean {
        if (this.blacklist.includes(structName)) return true;

        const parents = this.m_structParentMap[structName];
        if (parents != null) {
            for (const p of parents) {
                if (!this.structIgnored(p)) return false;
            }
            return true;
        }
        return false;
    }

    public entryIgnored(structName: string, entryName: string): boolean {
        return this.blacklist.includes(structName + '::' + entryName);
    }

    protected async readStructFile(f: string, isAdditional: boolean): Promise<void> {
        // console.log(`reading struct from ${f}`);
        const fileContent = await readXml(f);
        let xml: string;
        try {
            xml = xmljs.xml2json(fileContent, {compact: true, spaces: 0, ignoreComment: true});
        } catch(e) {
            console.error(e);
            console.log('xml parse failed:', f);
            await Alert.Instance.sendBuildFailureAlert(`xml文件格式错误: ${f.replaceAll('\\', '/')}\n${e instanceof Error ? e.message : String(e)}`);
            await Alert.Instance.alertErrorFile(f);
            process.exit(1);
        }
        const doc = JSON.parse(xml) as ConvertListDoc | StructDefSet;
        
        if ((doc as ConvertListDoc).ConvList) {
            this.readConvertListDoc(f, doc as ConvertListDoc);
            return;
        }

        const obj = doc as StructDefSet;
        if(obj.metalib.struct) {
            if(obj.metalib.struct instanceof Array) {
                for(let m of obj.metalib.struct) {
                    this.addStruct(m, isAdditional, f);
                }
            } else {
                this.addStruct(obj.metalib.struct, isAdditional, f);
            }
        }
        if(obj.metalib.macro) {
            if(obj.metalib.macro instanceof Array) {
                for(let m of obj.metalib.macro) {
                    this.addMacro(m, f);
                }
            } else {
                this.addMacro(obj.metalib.macro, f);
            }
        }
        if(obj.metalib.macros) {
            if(obj.metalib.macros.macro instanceof Array) {
                for(let m of obj.metalib.macros.macro) {
                    this.addMacro(m, f);
                }
            } else {
                this.addMacro(obj.metalib.macros.macro, f);
            }
        }
    }

    public async checkErrors(): Promise<void> {
        // 检查是否有类型结构未定义
        for (const k in this.m_structDefMap) {
            const w = this.m_structDefMap[k];
            for (const entry of w.def.entries) {
                const t = entry._attributes.type;
                if (isComplexType(t) && t != 'datetime' && t != 'date') {
                    if (!this.m_structDefMap[t]) {
                        this.errors.push({ msg: `未找到${k}:${entry._attributes.name}的类型定义${t}，可能导致转表失败` });
                    }
                }
            }
            if (w.def._attributes.sortkey) {
                const sortEntry = w.def.entries.find(v => v._attributes.name == w.def._attributes.sortkey);
                if (sortEntry == null) {
                    this.errors.push({ msg: `${k}的排序字段${w.def._attributes.sortkey}不存在，可能导致转表失败` });
                }
            }
        }

        if (this.errors.length > 0) {
            this.errors.forEach((v) => console.error(v.msg));
            await Alert.Instance.sendBuildFailureAlert(`xml错误: ${this.errors[0].msg.replaceAll('\\', '/')}`);
            if (this.errors[0].file) await Alert.Instance.alertErrorFile(this.errors[0].file);
            process.exit(1);
        }
    }

    private readConvertListDoc(file: string, doc: ConvertListDoc): void {
        const cnodes = doc.ConvList.ConvTree.CommNode;
        const binMap: Record<string, ConvertResNode> = {};
        for (const cnode of cnodes) {
            if (cnode.ResNode == null) continue;
            let nodes: ConvertResNode[];
            if (cnode.ResNode instanceof Array) {
                nodes = cnode.ResNode;
            } else {
                nodes = [cnode.ResNode];
            }
            for (const rnode of nodes) {
                const oldNode = binMap[rnode._attributes.BinFile];
                if (oldNode) {
                    this.errors.push({msg: `BinFile定义冲突: ${oldNode._attributes.Name} vs ${rnode._attributes.Name}`, file });
                }
                const struct = rnode._attributes.Meta.replace('_Flash', 'M');
                this.convMap[struct] = { name: rnode._attributes.Name, meta: rnode._attributes.Meta, xlsx: rnode._attributes.ExcelFile, sheet: rnode._attributes.IncludeSheet, xml: rnode._attributes.EntryMapFile, bin: rnode._attributes.BinFile, sort: rnode._attributes.Sort };
                binMap[rnode._attributes.BinFile] = rnode;
            }
        }
    }

    public getConvInfo(structName: string): IConvInfo {
        return this.convMap[structName];
    }

    protected addStruct(structDef: StructDef, isAdditional: boolean, file: string) {
        let isCliStruct = this.isClientStruct(structDef);
        let n = structDef._attributes.name;
        structDef._attributes.__rawName__ = n;
        if (this.rmFlashTail) {
            structDef._attributes.name = n = n.replace('_Flash', 'M');
        }
        let entries: EntryDef[];
        if(structDef.entry instanceof Array) {
            entries = structDef.entry;
        } else {
            entries = structDef.entry ? [structDef.entry] : [];
        }
        const entryMap: { [name: string]: EntryDef } = {};
        for (const entry of entries) {
            entryMap[entry._attributes.name] = entry;
            const options = entry._attributes.convertOption?.split(',') as ConvertOption[];
            if (options) {
                entry.isNoJson = options.includes('noJson');
                entry.isLang = options.includes('lang');
                entry.isOptional = options.includes('?') && !toolchain.projCfg.xml2json!.disableOptional;
                entry.isAtoi = options.includes('atoi');
                entry.isAtoiArr = options.includes('atoi_arr');
                entry.isUnique = options.includes('unique');
                // validate(LE1000)
                const validateStrs = options.filter(v => v.startsWith('validate('));
                for (const v of validateStrs) {
                    const mch = v.match(/validate\(LE(\d+)\)/);
                    if (mch) {
                        entry.validates = entry.validates || [];
                        entry.validates.push({ type: 'LE', value: Number(mch[1]), description: v });
                    }
                }
            }
        }
        for (const entry of entries) {
            // 检查是否有ref字段未定义（比如被误删）
            if (entry._attributes.refer && !entryMap[entry._attributes.refer]) {
                this.errors.push({ msg: `refer of ${n.replace(/M$/, '_Flash')}::${entry._attributes.name} missed: ${entry._attributes.refer}`, file });
            }
            // 检查string类型是否有定义size
            if (isLiteralStringType(entry._attributes.type) && !entry._attributes.size) {
                this.errors.push({ msg: `string type should have a size attribute defined: ${n.replace(/M$/, '_Flash')}::${entry._attributes.name}`, file });
            }
        }
        // 检查sort字段
        if (structDef._attributes.sortkey != null && entryMap[structDef._attributes.sortkey] == null && file != 'AdditionalMember.xml') {
            this.errors.push({ msg: `sort key not exists!` + file + ', ' + n.replace(/M$/, '_Flash'), file });
        }

        structDef.entries = entries;
        for (const e of entries) {
            if (e._attributes.value) {
                let arr = this.m_macroEntryMap[e._attributes.value];
                if (arr == null) this.m_macroEntryMap[e._attributes.value] = arr = [];
                arr.push({ struct: structDef._attributes.name, entry: e });
            }
            if(isComplexType(e._attributes.type)) {
                let arr = this.m_structParentMap[e._attributes.type];
                if (arr == null) this.m_structParentMap[e._attributes.type] = arr = [];
                arr.push(structDef._attributes.name);
            }
        }

        let oldDef = this.m_structDefMap[n];
        if(oldDef) {
            if(isAdditional) {
                for(let e of entries) {
                    e.isAdditional = true;
                    oldDef.def.entries.push(e);
                    if (this.rmFlashTail) {
                        e._attributes.type = e._attributes.type.replace('_Flash', 'M');
                    }
                    if(isComplexType(e._attributes.type)) {
                        if(isCliStruct && isComplexType(e._attributes.type)) {
                            this.addCliStruct(e._attributes.type);
                        }
                    }
                }
            } else {
                console.warn(`[WARNING]Structure redefinition for ${n}: ${file}`);
            }
        } else {
            this.m_structDefMap[n] = { def: structDef, file: file };

            if(isCliStruct) {
                this.m_cliStructs.push(n);
                for(let e of entries) {
                    if (this.rmFlashTail) {
                        e._attributes.type = e._attributes.type.replace('_Flash', 'M');
                    }
                    if(isComplexType(e._attributes.type)) {
                        this.addCliStruct(e._attributes.type);
                    }
                }
            }
        }
    }

    protected isClientStruct (struct: StructDef): boolean {
        return true;
    }

    protected addCliStruct(structName: string): void {
        if(!this.m_cliStructs.includes(structName)) {
            this.m_cliStructs.push(structName);
        }
    }

    public getStructDef(structName: string): StructDef | null {
        let wrap = this.m_structDefMap[structName];
        if(!wrap) {
            console.error('[ERROR] getStructDef: No struct definition!', structName);
            return null;
        }
        return wrap.def;
    }

    public mergeStructs(a: string, b: string) {
        let sa = this.getStructDef(a);
        if(!sa) return;
        let sb = this.getStructDef(b);
        if(!sb) return;

        let ea = sa.entries;
        let eb = sb.entries;

        let ema: { [ename: string]: EntryDef } = {};
        let emb: { [ename: string]: EntryDef } = {};
        for(let e of ea) {
            ema[e._attributes.name] = e;
        }
        for(let e of eb) {
            emb[e._attributes.name] = e;
        }

        sa.mergedEntries = [];
        sb.mergedEntries = [];
        for(let e of ea) {
            if(!emb[e._attributes.name]) {
                sb.mergedEntries.push(e);
            }
        }
        for(let e of eb) {
            if(!ema[e._attributes.name]) {
                sa.mergedEntries.push(e);
            }
        }
    }

    public makeStructCode(structName: string, type: 'class' | 'interface' | 'type', descGenerator?: (s: StructDef, e: EntryDef) => string[] | null): string {
        if (this.structIgnored(structName)) return '';
        let wrap = this.m_structDefMap[structName];
        if(!wrap) {
            console.error('[ERROR] makeStructCode: No struct definition!', structName);
            process.exit(1);
        }
        const def = wrap.def;
        // if (accurateUnion && def._attributes.class == 'union') {
        //     return '';
        // }
        let entries = def.entries;
        if(def.mergedEntries) {
            entries = entries.concat(def.mergedEntries);
        }
        // union字段处理
        const { selectEntry, unionEntry } = this.getUnionInfo(structName);
        const isUnion = selectEntry && unionEntry;
        const isUnionReq = isUnion && structName.endsWith('_Request');

        let code = '', declareStr: string;
        // request生成泛型映射
        if (isUnionReq) {
            declareStr = `export ${type} ${structName}<T extends number>`;
            if (type == 'type') declareStr += ' =';
            declareStr += ' ';
            
            const unionDef = this.getStructDef(unionEntry!._attributes.type)!;
            // 需包含response的select
            const rspStructName = structName.replace('_Request', '_Response');
            const rspUnionInfo = this.getUnionInfo(rspStructName);
            let sentries: EntryDef[] = unionDef.entries;
            if (rspUnionInfo.unionEntry) {
                const rspUnionDef = this.getStructDef(rspUnionInfo.unionEntry!._attributes.type);
                sentries = sentries.concat(rspUnionDef!.entries);
            }
            const rts = _.uniq(sentries.map(v => `Macros.${v._attributes.value}`)).join(' | ');
            code += `export type ${structName}Select = ${rts};\n`;

            const ueCodes: string[] = [], usedMap: Record<string, true> = {};
            // Request自身的select映射
            for (const ue of unionDef.entries) {
                usedMap[ue._attributes.value!] = true;
                const md = this.getMacro(ue._attributes.value!)!;
                if (md == null) {
                    console.error('no macro denition found for: ' + ue._attributes.value + '@' + structName);
                }
                let ueCode = `// Macros.${ue._attributes.value}\n[${md._attributes.value}]: {\n`;
                ueCode += `    ${selectEntry!._attributes.name}: Macros.${ue._attributes.value};\n`;
                const subUe: EntryDef = {
                    _attributes: {
                        desc: ue._attributes.desc,
                        name: ue._attributes.name,
                        type: ue._attributes.type
                    }
                }
                const subUeCode = this.makeBody(unionDef, [subUe]);
                ueCode += `    ${unionEntry!._attributes.name}: ` + indent('{\n' + subUeCode + '\n}', '    ', 1) + '\n}';
                ueCodes.push(ueCode);
            }
            // 补充Response的select映射
            if (rspUnionInfo.unionEntry) {
                const rspUnionDef = this.getStructDef(rspUnionInfo.unionEntry!._attributes.type)!;
                for (const ue of rspUnionDef.entries) {
                    if (usedMap[ue._attributes.value!]) continue;
                    const md = this.getMacro(ue._attributes.value!)!;
                    let ueCode = `// Macros.${ue._attributes.value}\n[${md._attributes.value}]: {\n`;
                    ueCode += `    ${selectEntry!._attributes.name}: Macros.${ue._attributes.value};\n}`;
                    ueCodes.push(ueCode);
                }
            }

            code += `interface _${structName}Map {\n`;
            code += indent(ueCodes.join(',\n'), '    ');
            code += '\n}\n';
        } else {
            declareStr = `export ${type} ${structName + (type == 'type' ? ' =' : '')} `;
        }

        const comment = this.makeStructComment(structName, wrap);
        if(comment) {
            code += comment;
        }
        code += declareStr;

        // 普通字段
        const normalEntries = isUnion ? entries.filter(v => v != selectEntry && v != unionEntry) : entries;
        const normalBody = this.makeBody(def, normalEntries, descGenerator);

        // union字段
        if (isUnion) {
            if (isUnionReq) {
                if (normalBody) {
                    code += '{\n';
                    code += normalBody + '\n} & (';
                }
                code += `T extends ${structName}Select ? _${structName}Map[T] : { ${selectEntry!._attributes.name}: T }`;
                if (normalBody) {
                    code += ')';
                }
                code += ';';
            } else {
                const unionDef = this.getStructDef(unionEntry!._attributes.type)!;
                const ueCodes: string[] = [];
                // 为兼容老代码，同时生成通用字段
                let gCode = '{\n';
                gCode += `    ${selectEntry!._attributes.name}: number;\n`;
                gCode += `    ${unionEntry!._attributes.name}: ${unionEntry!._attributes.type};\n`;
                gCode += '\n}';
                ueCodes.push(gCode);
    
                // 为每个select单独生成对应结构映射
                for (const ue of unionDef.entries) {
                    let ueCode = '{\n';
                    ueCode += `    ${selectEntry!._attributes.name}: Macros.${ue._attributes.value};\n`;
                    const subUe: EntryDef = {
                        _attributes: {
                            desc: ue._attributes.desc,
                            name: ue._attributes.name,
                            type: ue._attributes.type
                        }
                    }
                    const subUeCode = this.makeBody(unionDef, [subUe]);
                    ueCode += `    ${unionEntry!._attributes.name}: ` + indent('{\n' + subUeCode + '\n}', '    ', 1);
                    ueCode += '\n}';
                    ueCodes.push(ueCode);
                }
                if (normalBody) {
                    code += '{\n' + normalBody + '\n} & (\n' + indent(ueCodes.join(' | '), '    ') + '\n)';
                } else {
                    code += ueCodes.join(' | ');
                }
            }
        } else {
            code += '{\n' + normalBody + '\n}';
        }

        return code + '\n';
    }

    private getUnionInfo(structName: string): { selectEntry: EntryDef | null, unionEntry: EntryDef | null } {
        let wrap = this.m_structDefMap[structName];
        if(!wrap) {
            return { selectEntry: null, unionEntry: null };
        }
        const accurateUnion = toolchain.projCfg.xml2protocol?.accurateUnion == 'YES' && (structName.endsWith('_Request') || structName.endsWith('_Response') || structName.endsWith('_Notify'));
        if (!accurateUnion) return { selectEntry: null, unionEntry: null };

        let selectEntry: EntryDef | null = null, unionEntry: EntryDef | null = null;
        for(let e of wrap.def.entries) {
            if (e._attributes.select) {
                unionEntry = e;
                break;
            }
        }
        if (unionEntry) {
            for (let e of wrap.def.entries) {
                if (e._attributes.name == unionEntry._attributes.select) {
                    selectEntry = e;
                    break;
                }
            }
        }
        return { selectEntry, unionEntry };
    }

    private makeBody(def: StructDef, entries: EntryDef[], descGenerator?: (s: StructDef, e: EntryDef) => string[] | null): string {
        const isUnion = false;//def._attributes.class == 'union';

        const codes: string[] = [];
        for(let e of entries) {
            if (e._attributes.cname === cname_ClientUsed) continue;
            if (e.isNoJson) continue;

            const optional = e.isOptional ? '?' : '';
            const comments: string[] = [];
            if (e._attributes.desc) comments.push(e._attributes.desc);
            if (e._attributes.value) {
                // 增加union字段关联宏信息
                comments.push(`@union {@link Macros.${e._attributes.value}}`);
            }
            if (e._attributes.cname) {
                comments.push(`@cname ${e._attributes.cname}`);
            }
            if (e._attributes.refer) {
                comments.push(`@refer {@link ${def._attributes.name}.${e._attributes.refer}}`);
            }
            if (e._attributes.count) {
                comments.push(`@count Macros.${e._attributes.count}`);
            }
            if (e.isAtoi) {
                comments.push(`@atoi 本字段仅为字符串索引，原始字符串转存至strings.json`);
            }
            if (e.isAtoiArr) {
                comments.push(`@atoi_arr 本字段由工具自动转为数组`);
            }
            if (e._attributes.safeprintf) {
                comments.push(`@safeprintf`);
            }
            if (e._attributes.sparse_array == '1') {
                comments.push(`@sparse_array`);
            }
            if (descGenerator) {
                const s = descGenerator(def, e);
                if (s?.length) {
                    comments.push(...s);
                }
            }
            let ecode = '';
            if(comments.length > 0) {
                const cstr = makeComments(comments);
                ecode += indent(cstr, '    ') + '\n';
            }
            ecode += `    ${e._attributes.name}${optional}: ${this.getEntryType(e)};`
            codes.push(ecode);
        }
        let code: string;
        if (isUnion) {
            code = codes.map(v => `{\n${v}\n}`).join(' | ');
        } else {
            code = codes.join('\n');
        }
        return code;
    }

    protected makeStructComment(structName: string, wrap: StructWrap): string {
        if(wrap.def._attributes.desc) {
            return `/**${wrap.def._attributes.desc}(defined in ${wrap.file})*/\n`;
        }
        return '';
    }

    public makeSendMsgCode(structName: string, msgIds: string[]): string {
        if (this.structIgnored(structName)) return '';
        let genericParam1: string = '', genericParam2: string = '', genericParam3: string = '', paramStr: string, msgidStr: string;

        const { selectEntry, unionEntry } = this.getUnionInfo(structName);
        const isUnion = selectEntry && unionEntry;
        if (isUnion) {
            genericParam1 = `<T extends number>`;
            genericParam2 = '<T>';
            genericParam3 = ` as unknown as Protocol.${structName}<T>`;
        }

        if (msgIds.length == 1) {
            paramStr = '';
            msgidStr = `
    const msgid = Macros.${msgIds[0]};`;
        } else {
            paramStr = 'msgid: ' + msgIds.map((v) => `Macros.${v}`).join(' | ');
            msgidStr = '';
        }
        let code = `static get${structName}${genericParam1}(${paramStr}): Protocol.FyMsg<Protocol.${structName}${genericParam2}> {${msgidStr}
    SendMsgUtil._msg.m_stMsgHead.m_uiMsgID = msgid;
    let body: Protocol.${structName}${genericParam2} = SendMsgUtil._bodyObjects[msgid];
    if (null == body) {
        body = ${this.makeStructInitCode(structName)}${genericParam3};
        SendMsgUtil._bodyObjects[msgid] = body;
    }
    SendMsgUtil._msg.m_msgBody = body;
    return SendMsgUtil._msg;
}\n`;
        return code;
    }

    public makeStructInitCode(structName: string): string {
        const wrap = this.m_structDefMap[structName];
        if(!wrap) {
            console.error('[ERROR] makeStructInitCode: No struct definition!', structName);
            process.exit(1);
        }
        const def = wrap.def;
        let code = '';
        for(const e of def.entries) {
            if (code) code += ', ';
            let einit: string;
            if(e._attributes.count)
            {
                einit = '[]';
            } else {
                const type = e._attributes.type;
                if (NumberLike.includes(type)) {
                    einit = '0';
                } else if (StringLike.includes(type)) {
                    einit = '\'\'';
                } else {
                    einit = this.makeStructInitCode(type);
                }
            }
            code += `${e._attributes.name}: ${einit}`;
        }
        code = '{' + code + '}';
        return code;
    }

    protected getEntryType(e: EntryDef): string {
        let type = e._attributes.type;
        if (e.isAtoiArr) {
            type = 'number[]';
        } else if(NumberLike.includes(type) || e.isAtoi) {
            type = 'number';
        } else if(StringLike.includes(type)) {
            type = 'string';
        } 
        if(e._attributes.count || e._attributes.refer) {
            type += '[]';
        }
        return type;
    }

    protected addMacro(macro: MacrosDef, file: string): void {
        const fileName = path.basename(file);
        let book = this.m_macroFileMap[fileName];
        if(!book) this.m_macroFileMap[fileName] = book = { map: {}, list: [] };

        let n = macro._attributes.name;
        let old = book.map[n];
        if(old) {
            this.errors.push({ msg: `[ERROR]Macro redefinition for ${n} in ${file}: ${macro._attributes.value} vs old: ${old._attributes.value}`, file });
            for(let i = book.list.length - 1; i >= 0; i--) {
                if(book.list[i]._attributes.name == n) {
                    book.list.splice(i, 1);
                    break;
                }
            }
        }
        book.list.push(macro);
        book.map[n] = macro;
    }

    public getMacrosOfFile(fileName: string): MacrosDef[] {
        const out = this.m_macroFileMap[fileName].list;
        this.sortMacros(out);
        return out;
    }

    public getAllMacros(): MacrosDef[] {
        const out: MacrosDef[] = [];
        for (const fileName in this.m_macroFileMap) {
            out.push(... this.m_macroFileMap[fileName].list);
        }
        this.sortMacros(out);
        return out;
    }

    public getMacro(name: string, fileName?: string): MacrosDef | null {
        if (fileName) {
            const mm = this.m_macroFileMap[fileName];
            if (mm.map[name]) {
                return mm.map[name];
            }
        }
        for (const fn in this.m_macroFileMap) {
            const mm = this.m_macroFileMap[fn];
            if (mm.map[name]) {
                return mm.map[name];
            }
        }
        return null;
    }

    private sortMacros(arr: MacrosDef[]): void {
        const names: string[] = [];
        for (const m of arr) {
            names.push(m._attributes.name);
        }
        names.sort();
        const indexMap: { [name: string]: number } = {};
        for (let i = 0, len = names.length; i < len; i++) {
            indexMap[names[i]] = i;
        }
        arr.sort((a, b) => { return indexMap[a._attributes.name] - indexMap[b._attributes.name]} );
    }

    public getMacroRelatedEntry(macroName: string): IRelatedEntry[] {
        return this.m_macroEntryMap[macroName];
    }
}